10.2: Database SQLite

Materi:

Bab ini membahas SQLiteDatabase kerangka kerja Android dan kelas SQLiteOpenHelper. Bab ini bukanlah pengantar SQLite atau database SQL. Bab ini beranggapan bahwa Anda sudah familier dengan database SQL secara umum, dan pembangunan kueri SQL dasar. Periksa bab SQL Primer jika Anda memerlukan penyegaran.

Dari banyak opsi storage yang telah dibahas, menggunakan database SQLite adalah salah satu yang paling serbaguna dan praktis untuk diimplementasikan.

  • Database SQLite adalah solusi storage yang baik jika Anda memiliki data terstruktur yang perlu diakses dan disimpan secara persisten serta sering ditelusuri dan diubah.
  • Anda bisa menggunakan database sebagai storage utama untuk data aplikasi atau pengguna, atau Anda bisa menggunakannya untuk meng-cache serta menyediakan data yang diambil dari awan.
  • Jika Anda bisa menyatakan data berupa baris dan kolom, pertimbangkan database SQLite.
  • Penyedia materi, yang akan diperkenalkan dalam bab berikutnya, bekerja dengan bagus pada database SQLite.

Jika Anda menggunakan database SQLite, yang dinyatakan sebagai objek SQLiteDatabase, semua interaksi dengan database adalah melalui instance kelas SQLiteOpenHelper yang akan mengeksekusi permintaan dan mengelola database untuk Anda. Aplikasi Anda hanya boleh berinteraksi dengan SQLiteOpenHelper, yang akan dijelaskan di bawah ini.

Ada dua tipe data yang dikaitkan secara khusus dengan penggunaan database SQLite, Cursor dan ContentValues.

Cursor

SQLiteDatabase selalu menyajikan hasil berupa Cursor dalam format tabel yang menyerupai database SQL.

Anda bisa menganggap data sebagai larik baris. Kursor adalah pointer ke dalam satu baris data terstruktur. Kelas Cursor menyediakan metode untuk menggerakkan kursor melalui struktur data, dan metode untuk mendapatkan data dari bidang-bidang di setiap baris.

Kelas Cursor memiliki sejumlah subkelas yang mengimplementasikan kursor untuk tipe data tertentu.

  • SQLiteCursor mengekspos hasil kueri dari sebuah SQLiteDatabase. SQLiteCursor tidak disinkronkan secara internal, sehingga kode yang menggunakan SQLiteCursor dari beberapa thread harus melakukan sinkronisasi sendiri saat menggunakan SQLiteCursor.
  • MatrixCursor adalah implementasi kursor lengkap dan tidak tetap, yang didukung oleh larik objek yang secara otomatis meluaskan kapasitas internal bila diperlukan.

Beberapa operasi yang umum pada kursor adalah:

  • getCount() mengembalikan jumlah baris dalam kursor.
  • getColumnNames() mengembalikan larik string yang berisi nama semua kolom dalam rangkaian hasil dalam urutan pencantumannya dalam hasil.
  • getPosition() mengembalikan posisi kursor saat ini dalam rangkaian baris.
  • Getter tersedia untuk tipe data tertentu, seperti getString(int column) dan getInt(int column).
  • Operasi seperti moveToFirst() dan moveToNext() akan menggerakkan kursor.
  • close() membebaskan semua sumber daya dan membuat kursor menjadi tidak valid sama sekali. Ingat untuk menutup panggilan guna membebaskan sumber daya!

Memproses kursor

Jika panggilan metode mengembalikan ulangi pada hasil, ekstrak data, lakukan sesuatu dengan data, dan terakhir, harus menutup kursor untuk membebaskan memori. Jika tidak dilakukan, aplikasi Anda bisa mogok saat kehabisan memori.

Kursor dimulai sebelum baris hasil pertama, sehingga pada pengulangan pertama gerakkan kursor ke hasil pertama jika ada. Jika kursor kosong, atau baris terakhir sudah diproses, maka akan keluar dari loop. Jangan lupa menutup kursor bila Anda selesai menggunakannya. (Ini tidak boleh diulang terlalu sering.)

// Perform a query and store the result in a Cursor
Cursor cursor = db.rawQuery(...);
try {
    while (cursor.moveToNext()) {
        // Do something with the data
     }
} finally {
    cursor.close();
}

Jika menggunakan database SQL, Anda bisa mengimplementasikan kelas SQLiteOpenHelper untuk mengembalikan kursor ke aktivitas pemanggil atau adapter, atau Anda bisa mengonversi data ke format yang lebih cocok untuk adapter. Manfaat dari yang terakhir itu adalah pengelolaan kursor (dan penutupannya) ditangani oleh helper terbuka, dan antarmuka pengguna Anda tidak tergantung pada apa yang terjadi di backend. Lihat praktik Database SQLite untuk contoh implementasi.

ContentValues

Serupa dengan cara ekstra menyimpan data, instance ContentValues menyimpan data sebagai pasangan nilai-kunci, dalam ini kuncinya adalah nama kolom dan nilainya adalah nilai untuk sel. Satu instance ContentValues menyatakan satu baris tabel.

Metode insert() untuk database memerlukan nilai untuk mengisi baris yang diteruskan sebagai instance ContentValues.

ContentValues values = new ContentValues();
// Insert one row. Use a loop to insert multiple rows.
values.put(KEY_WORD, "Android");
values.put(KEY_DEFINITION, "Mobile operating system.");

db.insert(WORD_LIST_TABLE, null, values);

Mengimplementasikan database SQLite

Untuk mengimplementasikan database aplikasi Android, Anda perlu melakukan yang berikut ini.

  1. (Disarankan) Buat model data.
  2. Jadikan SQLiteOpenHelper sebagai subkelas

    1. Gunakan konstanta untuk nama tabel dan kueri pembuatan database

    2. Implementasikan onCreate untuk membuat SQLiteDatabase bersama tabel untuk data Anda

    3. Implementasikan onUpgrade()

    4. Implementasikan metode opsional

  3. Implementasikan metode query(), insert(), delete(), update(), count() dalam SQLiteOpenHelper.
  4. Dalam MainActivity Anda, buat instance SQLiteOpenHelper.
  5. Panggil metode SQLiteOpenHelper untuk digunakan bersama database Anda.

Keberatan:

  • Bila Anda mengimplementasikan metode, selalu masukkan operasi database ke dalam blok try/catch.
  • Aplikasi contoh tidak memvalidasi data pengguna. Bila menulis aplikasi untuk dipublikasikan, selalu pastikan data pengguna sesuai harapan untuk menghindari penyuntikan data buruk atau eksekusi perintah SQL yang berbahaya ke dalam database Anda.

Model data

Praktik yang baik adalah dengan membuat kelas yang menyatakan data Anda dengan getter dan setter.

Untuk database SQLite, instance kelas ini dapat menyatakan satu catatan, dan untuk database sederhana, satu baris dalam tabel.

public class WordItem {
    private int mId;
    private String mWord;
    private String mDefinition;
    // Getters and setters and more
}

Jadikan SQLiteOpenHelper sebagai subkelas

Open helper apa pun yang Anda buat harus memperluas SQLiteOpenHelper.

public class WordListOpenHelper extends SQLiteOpenHelper {

    public WordListOpenHelper(Context context) {
       super(context, DATABASE_NAME, null, DATABASE_VERSION);
       Log.d(TAG, "Construct WordListOpenHelper");
    }
}

Definisikan konstanta untuk nama tabel

Walaupun tidak diwajibkan, sudah biasa mendeklarasikan nama tabel, kolom, dan baris sebagai konstanta. Hal ini akan membuat kode lebih terbaca, lebih mudah mengubahi nama, dan kueri akan terlihat lebih mirip SQL. Anda bisa melakukannya dalam kelas open helper, atau dalam kelas publik tersendiri; Anda akan mengetahui selengkapnya tentang hal ini dalam bab tentang penyedia materi.

    private static final int DATABASE_VERSION = 1;
  // has to be 1 first time or app will crash
  private static final String WORD_LIST_TABLE = "word_entries";
  private static final String DATABASE_NAME = "wordlist";

  // Column names...
  public static final String KEY_ID = "_id";
  public static final String KEY_WORD = "word";

  // ... and a string array of columns.
  private static final String[] COLUMNS = {KEY_ID, KEY_WORD};

Definisikan kueri untuk membuat database

Anda memerlukan kueri yang membuat tabel untuk membuat database. Ini biasanya juga didefinisikan sebagai konstanta string. Contoh dasar ini membuat satu tabel dengan satu kolom untuk ID bertambah-otomatis dan kolom untuk menampung kata.

    private static final String WORD_LIST_TABLE_CREATE =
            "CREATE TABLE " + WORD_LIST_TABLE + " (" +
             KEY_ID + " INTEGER PRIMARY KEY, " +
             // will auto-increment if no value passed
             KEY_WORD + " TEXT );";

Implementasikan onCreate() dan buat database

Metode onCreate hanya dipanggil jika tidak ada database. Buat tabel Anda dalam metode, dan boleh menambahkan data awal.

@Override
public void onCreate(SQLiteDatabase db) { // Creates new database
   db.execSQL(WORD_LIST_TABLE_CREATE); // Create the tables
   fillDatabaseWithData(db); // Add initial data
   // Cannot initialize mWritableDB and mReadableDB here, because
   // this creates an infinite loop of on Create()
   // being repeatedly called.
}

Implementasikan onUpgrade()

Ini adalah metode yang diperlukan.

Jika database hanya berfungsi sebagai cache untuk data yang juga disimpan online, Anda bisa menghapus tabel dan membuat ulang setelah peningkatan versi selesai.

Catatan: Jika database adalah storage utama, Anda harus mengamankan data pengguna sebelum melakukannya karena operasi ini akan memusnahkan semua data. Lihat bab mengenai Menyimpan Data.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // SAVE USER DATA FIRST!!!
        Log.w(WordListOpenHelper.class.getName(),
                "Upgrading database from version " + oldVersion + " to "
                        + newVersion + ", which will destroy all old data");
        db.execSQL("DROP TABLE IF EXISTS " + WORD_LIST_TABLE);
        onCreate(db);
}

Metode opsional

Kelas open helper menyediakan metode tambahan yang bisa Anda ganti bila diperlukan.

  • onDowngrade()—Implementasi default menolak penurunan versi.
  • onConfigure()—dipanggil sebelum onCreate. Gunakan ini hanya untuk memanggil metode yang mengonfigurasi parameter koneksi database.
  • onOpen()—Pekerjaan apa pun selain konfigurasi yang harus dilakukan sebelum database dibuka.

Operasi database

Walaupun bisa memanggil metode dalam open helper yang diinginkan dan mengembalikan yang Anda pilih ke aktivitas pemanggil, lebih baik lanjutkan dengan metode query(), insert(), delete(), update(), count() standar yang sesuai dengan API database dan penyedia materi. Menggunakan format ini akan mempermudah penambahan penyedia materi atau pemuat di masa mendatang, dan memudahkan orang lain dalam memahami kode Anda.

Diagram berikut menampilkan cara mendesain API yang berbeda agar konsisten dan jelas. Desain API Anda agar konsisten dengan menggunakan kueri

query()

Metode kueri yang diimplementasikan dalam kelas open helper Anda bisa mengambil dan mengembalikan tipe data apa pun yang diperlukan antarmuka pengguna.

Karena open helper menyediakan metode praktir untuk menyisipkan, menghapus, dan memperbarui baris, metode kueri Anda tidak perlu generik dan mendukung operasi ini.

Secara umum, metode kueri Anda hanya boleh mengizinkan kueri yang diperlukan oleh aplikasi dan bukan untuk keperluan umum.

Database menyediakan dua metode untuk mengirimkan kueri: SQLiteDatabase.rawQuery() dan SQLiteDatabase.query(), bersama sejumlah opsi untuk argumen.

SQLiteDatabase.rawQuery().rawQuery()

Metode kueri open helper bisa membentuk kueri SQL dan mengirimkannya sebagai rawQuery ke database yang mengembalikan kursor. Jika data disediakan oleh aplikasi dan dikontrol penuh, Anda bisa menggunakan rawQuery().

rawQuery(String sql, String[] selectionArgs)
  • Parameter pertama untuk db.rawquery() adalah string kueri SQLite.
  • Parameter kedua berisi argumen.
    cursor = mReadableDB.rawQuery(queryString, selectionArgs);
    

SQLiteDatabase.query()

Jika Anda memproses data yang disediakan pengguna, bahkan setelah validasi, lebih aman membentuk kueri dan menggunakan versi metode SQLiteDatabase.query() untuk database. Argumen adalah apa yang Anda harapkan dalam SQL dan didokumentasikan dalam dokumentasi SQLiteDatabase.

Cursor query (boolean distinct,  String table, String[] columns, String selection,
                String[] selectionArgs, String groupBy, String having,  
                String orderBy,String limit)

Inilah contoh dasarnya:

String[] columns = new String[]{KEY_WORD};
String where =  KEY_WORD + " LIKE ?";
searchString = "%" + searchString + "%";
String[] whereArgs = new String[]{searchString};
cursor = mReadableDB.query(WORD_LIST_TABLE, columns, where, whereArgs, null, null, null);

Contoh lengkap open helper query()

public WordItem query(int position) {
   String query = "SELECT  * FROM " + WORD_LIST_TABLE +
           " ORDER BY " + KEY_WORD + " ASC " +
           "LIMIT " + position + ",1";

   Cursor cursor = null;
   WordItem entry = new WordItem();

   try {
       if (mReadableDB == null) {mReadableDB = getReadableDatabase();}
       cursor = mReadableDB.rawQuery(query, null);
       cursor.moveToFirst();
       entry.setId(cursor.getInt(cursor.getColumnIndex(KEY_ID)));
       entry.setWord(cursor.getString(cursor.getColumnIndex(KEY_WORD)));
   } catch (Exception e) {
       Log.d(TAG, "EXCEPTION! " + e);
   } finally {
       // Must close cursor and db now that we are done with it.
       cursor.close();
       return entry;
   }
}

insert()

Metode insert() open helper memanggil SQLiteDatabase.insert(), yaitu metode SQLiteDatabase praktis yang digunakan untuk menyisipkan baris ke dalam database. (Metode ini praktis, karena Anda tidak perlu menulis kueri SQL sendiri.)

Format

long insert(String table, String nullColumnHack, ContentValues values)
  • Argumen pertama adalah nama tabel.
  • Argumen kedua adalah sebuah String nullColumnHack. Ini adalah solusi yang memungkinkan Anda untuk menyisipkan baris kosong. Lihat dokumentasi untuk insert(). Gunakan null.
  • Argumen ketiga harus berupa kontainer [ContentValues] bersama nilai-nilai untuk mengisi baris. Contoh ini hanya memiliki satu kolom; untuk tabel dengan banyak kolom, tambahkan
  • Metode database mengembalikan ID item yang baru disisipkan, dan Anda harus meneruskannya ke aplikasi.

Contoh

newId = mWritableDB.insert(WORD_LIST_TABLE, null, values);

delete()

Metode delete dari open helper memanggil metode delete() database, yang praktis digunakan sehingga Anda tidak perlu menulis kueri SQL seluruhnya.

Format

int delete (String table, String whereClause, String[] whereArgs)
  • Argumen pertama adalah nama tabel.
  • Argumen kedua adalah sebuah klausa WHERE.
  • Argumen ketiga adalah argumen untuk klausa WHERE.

Anda bisa menghapus menggunakan kriteria apa pun, dan metode akan mengembalikan jumlah item yang sebenarnya dihapus, yang juga harus dikembalikan oleh open helper.

Contoh

deleted = mWritableDB.delete(WORD_LIST_TABLE,
                    KEY_ID + " =? ", new String[]{String.valueOf(id)});

update()

Metode update dari open helper memanggil metode update() database, yang praktis digunakan sehingga Anda tidak perlu menulis kueri SQL seluruhnya. Argumen tersebut sudah familier dari metode sebelumnya, dan onUpdate mengembalikan jumlah baris yang diperbarui.

Format

int update(String table, ContentValues values,
    String whereClause, String[] whereArgs)
  • Argumen pertama adalah nama tabel.
  • Argumen kedua harus berupa ContentValues bersama nilai-nilai baru untuk baris tersebut.
  • Argumen ketiga adalah klausa WHERE.
  • Argumen keempat adalah argumen untuk klausa WHERE.

Contoh

ContentValues values = new ContentValues();
values.put(KEY_WORD, word);
mNumberOfRowsUpdated = mWritableDB.update(WORD_LIST_TABLE,
values, // new values to insert
KEY_ID + " = ?",
new String[]{String.valueOf(id)});

count()

Metode count() mengembalikan jumlah entri dalam database. Jika menggunakan RecyclerView.Adapter, Anda harus mengimplementasikan getItemCount(), yang perlu mendapatkan sejumlah baris dari open helper, yang perlu didapat dari database.

Di adapter

@Override
public int getItemCount() {
    return (int) mDB.count();
}

Di open helper

public long count(){
    if (mReadableDB == null) {mReadableDB = getReadableDatabase();}
    return DatabaseUtils.queryNumEntries(mReadableDB, WORD_LIST_TABLE);
}

queryNumEntries()) adalah metode di kelas DatabaseUtils publik, yang menyediakan banyak metode praktis untuk digunakan bersama kursor, database, dan juga penyedia materi.

Buat Instance Open Helper

Untuk menangani database, dalam MainActivity, di onCreate, panggil:

mDB = new WordListOpenHelper(this);

Bekerja dengan database

Mengombinasikan backend SQLiteDatabase dengan RecyclerView untuk menampilkan data merupakan pola yang umum.
Komponen umum digunakan bersama aplikasi yang menggunakan database SQLite

Misalnya:

  • Menekan FAB bisa memulai aktivitas yang akan mendapatkan masukan dari pengguna dan menyimpannya ke dalam database sebagai item baru atau diperbarui.
  • Menggesek suatu item bisa menghapusnya setelah pengguna mengonfirmasi penghapusan.

Transaksi

Gunakan transaksi

  • saat melakukan beberapa operasi yang semuanya harus diselesaikan agar database konsisten, misalnya, memperbarui penetapan harga item terkait untuk kejadian penjualan.
  • untuk batch beberapa operasi independen guna meningkatkan kinerja, seperti penyisipan massal.

Transaksi bisa disarangkan, dan kelas SQLiteDatabase menyediakan metode tambahan untuk mengelola transaksi tersarang. Lihat dokumentasi referensi SQLiteDatabase.

Idiom transaksi

db.beginTransaction();
try {
  ...
  db.setTransactionSuccessful();
} finally {
  db.endTransaction();
}

Mencadangkan database

Ada baiknya Anda mencadangkan database aplikasi.

Anda bisa melakukannya menggunakan opsi Cloud Backup yang dibahas dalam bab Opsi Storage.

Mengirim database bersama aplikasi Anda

Kadang-kadang Anda mungkin ingin menyertakan database yang sudah terisi bersama aplikasi. Ada sejumlah cara untuk melakukannya, dan konsekuensi dari setiap cara tersebut.

  • Sertakan perintah SQL bersama aplikasi dan perintahkan untuk membuat database dan menyisipkan data pada penggunaan pertama. Inilah yang pada dasarnya akan Anda lakukan dalam praktik storage data. Jika jumlah data yang ingin dimasukkan dalam database kecil, hanya satu contoh agar pengguna bisa melihat sesuatu, Anda bisa menggunakan metode ini.
  • Kirimkan data bersama APK sebagai sumber daya, dan bangun database saat pengguna membuka aplikasi untuk pertama kali. Metode ini sama dengan metode pertama, namun sebagai ganti mendefinisikan data dalam kode, Anda memasukkannya dalam sumber daya, misalnya, dalam format CSV. Selanjutnya Anda bisa membaca data dengan aliran masukan dan menambahkannya ke database.
  • Bangun dan isi dahulu database SQLite lalu sertakan dalam APK. Dengan metode ini, tulis aplikasi yang akan membuat dan mengisi database. Anda bisa melakukan ini pada emulator. Selanjutnya Anda bisa menyalin file tersebut di tempat penyimpanan database sebenarnya (direktori "/data/data/YOUR_PACKAGE/databases/") dan menyertakannya sebagai aset bersama aplikasi. Saat aplikasi dimulai untuk pertama kali, salin kembali file database ke dalam direktori "/data/data/YOUR_PACKAGE/databases/".

Kelas SQLiteAssetHelper, yang bisa Anda unduh dari Github, memperluas SQLiteOpenHelper untuk membantu Anda melakukannya. Dan entri blog Stackoverflow akan membahas topik ini lebih detail.

Perhatikan, untuk database yang lebih besar, mengisi database harus dilakukan di latar belakang, dan aplikasi Anda tidak akan mogok jika belum ada database, atau database masih kosong.

Praktik terkait

Dokumentasi praktik terkait ada di Dasar-Dasar Developer Android: Praktik.

Ketahui selengkapnya

results matching ""

    No results matching ""